using System;
using System.Collections.Generic;
using System.Text;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Dynamics.Springs;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Net;

namespace GrapplingHookGameData
{
    public class Player : PhysicsObject
    {
        #region Fields
        protected GraphicsDevice graphicsDevice;

        public bool playerStanding = false;
        public float playerDirection = 1.0f;

        public int health = Constants.PLAYER_HEALTH;
        private int throwCooldown = 0;

        //store weapon objects player have. There is a maximum number to prevent too many on screen, as well as not needing to instance new objects all the time
        private List<Grenade> grenades = new List<Grenade>(Constants.PLAYER_MAX_GRENADES);
        private List<Shuriken> shurikens = new List<Shuriken>(Constants.PLAYER_MAX_SHURIKENS);
    

        //rope
        SpringRectangleRope rope;


        public PlayerMoveActions nextMoveAction = PlayerMoveActions.NONE;
        public enum PlayerMoveActions
        {
            NONE, RIGHT, LEFT, FIRE, FIRE_ROPE, DISCONNECT_ROPE
        }

        public PlayerRopeActions nextRopeAction = PlayerRopeActions.NONE;
        public enum PlayerRopeActions
        {
            NONE, FIRE_ROPE, DISCONNECT_ROPE
        }

        public PlayerWeaponActions nextGunAction = PlayerWeaponActions.NONE;
        public enum PlayerWeaponActions
        {
            NONE, GRENADE, SHURIKEN
        }

        private enum AnimationState
        {
            STANDING, RUNNING
        }
        private AnimationState currentAnimationState = AnimationState.STANDING;

        private Animation runningAnimation;
        #endregion

        #region Properties
        public int Wins {get; private set;}
        #endregion

        #region Initialization

        public Player(IGameDataCallbacks gameCallback, Vector2 startPosition)
            : base(gameCallback, startPosition, Constants.PLAYER_WIDTH, Constants.PLAYER_HEIGHT, "player/SpriteSheet", true)
        {
            //create all grenades
            int i = Constants.PLAYER_MAX_GRENADES;
            while(  i-- > 0)
            {
                grenades.Add(new Grenade(gameCallback, Vector2.Zero, this));
            }
            //create all shurikens
            i = Constants.PLAYER_MAX_SHURIKENS;
            while (i-- > 0)
            {
                shurikens.Add(new Shuriken(gameCallback, Vector2.Zero, this));
            }
            rope = new SpringRectangleRope(gameCallback, this);

        }



        public void LoadContent(GraphicsDevice graphicsDevice, ContentManager content, PhysicsSimulator physicsSimulator)
        {
            base.LoadContent(content, physicsSimulator);
            this.graphicsDevice = graphicsDevice;

            currentFrame = spriteSheet.GetIndex("player_standing0000");
            runningAnimation = new Animation(spriteSheet, "player_running", 6, Constants.PLAYER_FRAME_ANIMATION_TIME_MS);


            Body = BodyFactory.Instance.CreateRectangleBody(physicsSimulator, Width, Height, Constants.PLAYER_MASS);
            Body.Position = position;
            Body.MomentOfInertia = float.PositiveInfinity;
            Body.LinearDragCoefficient = 1;
            Geom = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator, Body, Width, Height);
            Geom playerFeetGeom = GeomFactory.Instance.CreateRectangleGeom(physicsSimulator, Body, Width, 5, new Vector2(0, Height / 2f + 2.5f), Constants.COLLISION_GRID_SIZE);
            playerFeetGeom.CollisionResponseEnabled = true;
            Geom.CollisionGroup = 1;
            Geom.FrictionCoefficient = 0.8f;
            Geom.Tag = this;
            playerFeetGeom.CollisionGroup = 1;
            playerFeetGeom.OnCollision += new Geom.CollisionEventHandler(onCollision);

            rope.LoadContent(graphicsDevice, physicsSimulator, content);

            //load grenades
            foreach (Grenade g in grenades)
            {
                g.LoadContent(content, physicsSimulator);
            }
            //load shurikens
            foreach (Shuriken s in shurikens)
            {
                s.LoadContent(content, physicsSimulator);
            }

        }
        #endregion

        #region Update

        /// <summary>
        /// Update physics forces on player depending on next requested action.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime, bool isHost)
        {
            base.Update(gameTime, isHost);

            //update rope
            rope.Update(gameTime, isHost);

            int force = 100;
            if (playerStanding) force = 700;
            switch (nextMoveAction)
            {
                case PlayerMoveActions.RIGHT:
                    Body.ApplyForce(new Vector2(force, 0));
                    playerDirection = 1.0f;
                    break;
                case PlayerMoveActions.LEFT:
                    Body.ApplyForce(new Vector2(-force, 0));
                    playerDirection = -1.0f;
                    break;
            }
            nextMoveAction = PlayerMoveActions.NONE;

            switch (nextRopeAction)
            {
                case PlayerRopeActions.DISCONNECT_ROPE:
                    rope.DisconnectRope();
                    break;
                case PlayerRopeActions.FIRE_ROPE:
                    rope.FireAnchor();
                    break;
            }
            nextRopeAction = PlayerRopeActions.NONE;

            switch (nextGunAction)
            {
                case PlayerWeaponActions.GRENADE:
                    ThrowGrenade();
                    break;
                case PlayerWeaponActions.SHURIKEN:
                    ThrowShuriken();
                    break;
            }
            nextGunAction = Player.PlayerWeaponActions.NONE;

            //update grenades
            foreach (Grenade g in grenades)
            {
                g.Update(gameTime, isHost);
            }

            //update shurikens
            foreach (Shuriken s in shurikens)
            {
                s.Update(gameTime, isHost);
            }

            if (throwCooldown > 0) throwCooldown -= gameTime.ElapsedGameTime.Milliseconds;

            //update player animation
            UpdateAnimation(gameTime);
        }

        /// <summary>
        /// updates the players animation based on what he is doing
        /// </summary>
        private void UpdateAnimation(GameTime gameTime)
        {
            switch (currentAnimationState)
            {
                    //If standing, see if we should be running instead
                case AnimationState.STANDING:
                    if (Math.Abs(Body.LinearVelocity.X) > 40 || (!playerStanding && (nextMoveAction == PlayerMoveActions.LEFT || nextMoveAction == PlayerMoveActions.RIGHT)))
                    {
                        currentAnimationState = AnimationState.RUNNING;
                        currentFrame = runningAnimation.Start();
                    }
                    break;
                //If running, see if we should be standing instead, otherwise update animation
                case AnimationState.RUNNING:
                    if (Math.Abs(Body.LinearVelocity.X) < 15  )
                    {
                        currentAnimationState = AnimationState.STANDING;
                        currentFrame = spriteSheet.GetIndex("player_standing0000");
                    }
                    else
                    {
                        currentFrame = runningAnimation.Update(gameTime);
                    }
                    break;
            }

        
        }

        /// <summary>
        /// Called when player feet touches something
        /// </summary>
        /// <param name="geometry1"></param>
        /// <param name="geometry2"></param>
        /// <param name="contactList"></param>
        /// <returns></returns>
        public bool onCollision(Geom geometry1, Geom geometry2, ContactList contactList)
        {
            if (geometry2.Body.IsStatic) playerStanding = true;
            return false;
        }


        #endregion

        #region Actions

        public void ThrowGrenade()
        {
            //see if ready to throw
            if (throwCooldown > 0) return;

            //see if grenades are available to throw
            Grenade availableGrenade = null;
            foreach (Grenade g in grenades)
            {
                if (g.CurrentState == Grenade.GrenadeState.INACTIVE)
                {
                    availableGrenade = g;
                    break;
                }
            }

            if (availableGrenade != null)
            {
                availableGrenade.Position = Body.Position + new Vector2(playerDirection * Constants.PLAYER_WIDTH, -Constants.PLAYER_HEIGHT / 2);
                availableGrenade.Body.ApplyImpulse(new Vector2(playerDirection * Constants.THROW_IMPULSE, -Math.Abs(playerDirection * Constants.THROW_IMPULSE)));
                availableGrenade.Body.ApplyAngularImpulse(100.0f * playerDirection);
                availableGrenade.Body.LinearVelocity = this.Body.LinearVelocity;        //inherit player velocity
                availableGrenade.Activate();
                throwCooldown = Constants.PLAYER_THROW_COOLDOWN_MS;
            }
        }

        public void ThrowShuriken()
        {
            //see if ready to throw
            if (throwCooldown > 0) return;

            //see if shurikens are available to throw
            Shuriken availableShuriken = null;
            foreach (Shuriken s in shurikens)
            {
                if (s.CurrentState == Shuriken.ShurikenState.INACTIVE)
                {
                    availableShuriken = s;
                    break;
                }
            }

            if (availableShuriken != null)
            {
                availableShuriken.Position = Body.Position + new Vector2(playerDirection * Constants.PLAYER_WIDTH, -Constants.PLAYER_HEIGHT / 2);
                availableShuriken.Body.ApplyImpulse(new Vector2(playerDirection * Constants.THROW_IMPULSE, 0));
                availableShuriken.Body.ApplyAngularImpulse(100.0f * playerDirection);
                availableShuriken.Body.LinearVelocity = this.Body.LinearVelocity;        //inherit player velocity
                availableShuriken.Activate();
                throwCooldown = Constants.PLAYER_THROW_COOLDOWN_MS;
            }
        }


        public void AddWin()
        {
            Wins++;
        }

        #endregion

        #region Draw
        public override void Draw(SpriteBatch spriteBatch)
        {
            if (playerDirection < 0) spriteFlip = SpriteEffects.FlipHorizontally;
            else spriteFlip = SpriteEffects.None;
            base.Draw(spriteBatch);

            rope.Draw(spriteBatch);

            //draw grenades
            foreach (Grenade g in grenades)
            {
                g.Draw(spriteBatch);
            }


            //draw shurikens
            foreach (Shuriken s in shurikens)
            {
                s.Draw(spriteBatch);
            }
        }
        #endregion

        #region Networking
        /// <summary>
        /// Writes the player data that is needed by clients to draw this player and related objects
        /// </summary>
        /// <param name="packetWriter"></param>
        public void WriteDataToClients(PacketWriter packetWriter)
        {
            packetWriter.Write(Position);
            packetWriter.Write(health);

            foreach (Grenade g in grenades)
            {
                g.WriteDataToClients(packetWriter);
            }
            foreach (Shuriken s in shurikens)
            {
                s.WriteDataToClients(packetWriter);
            }

            rope.WriteDataToClients(packetWriter);
        
        }

        /// <summary>
        /// Read the player data that is needed by clients to draw this player
        /// </summary>
        /// <param name="packetWriter"></param>
        public void ReadDataFromHost(PacketReader packetReader)
        {
            //clients set the position directly
            Position = packetReader.ReadVector2();
            health = packetReader.ReadInt32();

            foreach (Grenade g in grenades)
            {
                g.ReadDataFromHost(packetReader);
            }
            foreach (Shuriken s in shurikens)
            {
                s.ReadDataFromHost(packetReader);
            }


            rope.ReadDataFromHost(packetReader);

        }

        /// <summary>
        /// Writes the player data that is needed by the host. This is the actions that the player wishes to take.
        /// </summary>
        /// <param name="packetWriter"></param>
        public void WriteDataToHost(PacketWriter packetWriter)
        {
            packetWriter.Write((int)nextMoveAction);
            packetWriter.Write((int)nextRopeAction);
            packetWriter.Write((int)nextGunAction);
            nextMoveAction = Player.PlayerMoveActions.NONE;
            nextRopeAction = Player.PlayerRopeActions.NONE;
            nextGunAction = Player.PlayerWeaponActions.NONE;
        }

        /// <summary>
        /// Read the player data that is needed by host to update the player. This is the actions that the player wishes to take.
        /// </summary>
        /// <param name="packetWriter"></param>
        public void ReadDataFromClient(PacketReader packetReader)
        {
            nextMoveAction = (Player.PlayerMoveActions)packetReader.ReadInt32();
            nextRopeAction = (Player.PlayerRopeActions)packetReader.ReadInt32();
            nextGunAction = (Player.PlayerWeaponActions)packetReader.ReadInt32();
        }
        #endregion
    }
}
